package sg.edu.nus.iss;

import org.jgap.FitnessFunction;
import org.jgap.IChromosome;

/**
 * 
 * @author Xu Guoneng
 *
 */
public class StackFitnessFunction extends FitnessFunction{

	private static final long serialVersionUID = 4705641678841874618L;
	
	private final Package[] packages;
	private final double avg_weight;
	
	public StackFitnessFunction(Package[] packages){
		this.packages = packages;
		
		double sum_weight = 0;
		for(Package p : packages){
			sum_weight += p.getWeight();
		}
		avg_weight = sum_weight / packages.length;
	}
	
	/**
	   * Calculates the difference in weight between the 16 stacks of packages. The
	   * lower the difference the better the solution is.
	   *
	   * @param subject the Chromosome to be evaluated
	   * @return fitness of our problem (smaller is better here)
	   *
	   */
	public double evaluate(IChromosome subject) {
		double unadjust = 0;
		
		if(Main.CONVERGE_TYPE == 0){
			unadjust = calLogDiff(subject);
		}
		else{
			unadjust = calSumSqDiff(subject);
		}
		
		if(Main.PENALTY_ON){
			return unadjust * (1 + computeSumStackPenalty(subject));
		}
		else{
			return unadjust;
		}
	}
	
	public double calLogDiff(IChromosome subject){
		double res = 0;
		for(int i=0; i<Main.STACK_NUM; i++){
			double delta = calStackAbsDiff(i, subject);
			res += delta;
		}
		return Math.log(res);
	}
	
	public double calSumSqDiff(IChromosome subject){
		double res = 0;
		for(int i=0; i<Main.STACK_NUM; i++){
			double delta = calStackAbsDiff(i, subject);
			res += delta * delta;
		}
		return res;
	}
	
	/**
	 * Assume Stack index starts from 0
	 * @param stack
	 * @param subject
	 * @return
	 */
	private double calStackAbsDiff(int stack, IChromosome subject){
		return Math.abs(calStackAvgWeight(stack, subject) - avg_weight);
	}
	
	private double calStackAvgWeight(int stack, IChromosome subject){
		double stack_sum = 0;
		for(int i=0; i < Main.STACK_CAPACITY; i++){
			int index = stack * Main.STACK_CAPACITY + i;
			int packageId = (Integer)subject.getGene(index).getAllele();
			stack_sum += packages[packageId].getWeight();
		}
		
		return stack_sum / Main.STACK_CAPACITY;
	}
	
	
	private double computeSumStackPenalty(IChromosome subject){
		double sum = 0;
		for(int i=0; i<Main.STACK_NUM; i++){
			sum += computeIndivStatckPenalty(i, subject);
		}
		
		return sum;
	}
	
	private double computeIndivStatckPenalty(int stack, IChromosome subject){
		int sum_category = 0;
		
		for(int i=0; i < Main.STACK_CAPACITY; i++){
			int index = stack * Main.STACK_CAPACITY + i;
			int packageId = (Integer)subject.getGene(index).getAllele();
			sum_category += packages[packageId].getCategoryCd();
		}
		
		switch(sum_category){
			case 0:
			case 4:  return 0;
			case 1:
			case 3:  return Main.MAX_PENALTY / 2;
			default: return Main.MAX_PENALTY;	//case "2"
		}
	}
}
